/*
  Copyright 2002-2003 The University of Texas at Austin
  
	Authors: Anthony Thane <thanea@ices.utexas.edu>
			 Vinay Siddavanahalli <skvinay@cs.utexas.edu>
	Advisor: Chandrajit Bajaj <bajaj@cs.utexas.edu>

  This file is part of Volume Rover.

  Volume Rover is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  Volume Rover is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with iotree; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>
#include <math.h>

#include <QMenu>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QColorDialog>
#include <QMouseEvent>
#include <QToolTip>

#include "XoomedIn.h"

//using namespace PBVP;

const bool HorizontalMap = true;
const bool InvertDensity = false;
const bool InvertAlpha = true;

/*
const bool HorizontalMap = false;
const bool InvertDensity = false;
const bool InvertAlpha = false;
*/

const int CLICK_RANGE = 5;
const int ALPHA_NODE_SIZE = 5;

XoomedIn::XoomedIn( TransferMap* transferMap, QWidget *parent, const char *name )
: QFrame( parent)
{
	setFrameStyle( QFrame::Panel | QFrame::Sunken );
	m_TransferMap = transferMap;
	m_RangeMin = 0.0;
	m_RangeMax = 1.0;
	m_MinVal = 0.0;
	m_MaxVal = 1.0;
	m_MoveMode = NO_MOVE;

	m_SpecIsoval = 0;
	m_SpecArea = 0;
	m_SpecMinVol = 0;
	m_SpecMaxVol = 0;
	m_SpecGradient = 0;

//	m_CTVerts = 0;
//	m_CTEdges = 0;
	m_NumCTVerts = 0;
	m_NumCTEdges = 0;

	m_DrawContourSpec = false;
	m_DrawContourTree = false;
	m_DrawAlphaMap = true;

	loadMapAction = new QAction("Load Map", this);
	connect(loadMapAction, SIGNAL(triggered()), this, SLOT(loadMap()));
	saveMapAction = new QAction("Save Map", this);
	connect(saveMapAction, SIGNAL(triggered()), this, SLOT(saveMap()));
	popupMenu = new QMenu(this);
	popupMenu->addAction(loadMapAction);
	popupMenu->addAction(saveMapAction);

	addColorAction = new QAction("Add Color Node", this);
	connect(addColorAction, SIGNAL(triggered()), this, SLOT(addColorNode()));
	addAlphaAction = new QAction("Add Alpha Node", this);
	connect(addAlphaAction, SIGNAL(triggered()), this, SLOT(addAlphaNode()));
	addContourAction = new QAction("Add IsoContour", this);
	connect(addContourAction, SIGNAL(triggered()), this, SLOT(addContourNode()));
	addNodeMenu = new QMenu("Add", this);
	addNodeMenu->addAction(addColorAction);
	addNodeMenu->addAction(addAlphaAction);
	addNodeMenu->addAction(addContourAction);
	popupMenu->addMenu(addNodeMenu);

	editNodeAction = new QAction("Edit", this);
	connect(editNodeAction, SIGNAL(triggered()), this, SLOT(editSelectedNode()));
	deleteNodeAction = new QAction("Delete", this);
	connect(deleteNodeAction, SIGNAL(triggered()), this, SLOT(deleteSelectedNode()));
	popupMenu->addAction(editNodeAction);
	popupMenu->addAction(deleteNodeAction);

	connect(this, SIGNAL(functionChanged()), this, SLOT(update()));
	connect(this, SIGNAL(everythingChanged()), this, SLOT(update()));
	connect(this, SIGNAL(isocontourNodeAdded(int, double, double, double, double)), this, SLOT(update()));
	connect(this, SIGNAL(isocontourNodeDeleted(int)), this, SLOT(update()));
	// setToolTip("Zoom-in view of Transfer Map");
}

XoomedIn::~XoomedIn()
{
	if ( m_SpecIsoval) {
		free(m_SpecIsoval);
		m_SpecIsoval = 0;
	}
	if ( m_SpecArea ) {
		free(m_SpecArea);
		m_SpecArea = 0;
	}
	if ( m_SpecMinVol ) {
		free(m_SpecMinVol);
		m_SpecMinVol = 0;
	}
	if ( m_SpecMaxVol ) {
		free(m_SpecMaxVol);
		m_SpecMaxVol = 0;
	}
	if ( m_SpecGradient ) {
		free(m_SpecGradient);
		m_SpecGradient = 0;
	}

}

QString XoomedIn::tip( const QPoint & p)
{
	// check if alpha / color, return accordingly
	int x = p.x();
	int y = p.y();
	double alpha = 0.0;
	double density = 0.0;
	double r = 0.0, g = 0.0, b = 0.0;
	QString str;

	if ( getAlphaNodesHorizontal( x, y, &alpha, &density ) ) {
		str = QString("Alpha = %1, density = %2").arg(alpha).arg(density);
		return str;
	} else if ( getColorNodesHorizontal(x, y, &r, &g, &b, &density) ) {
		str = QString("r,g,b = [%1,%2,%3], density = %4").arg(r).arg(g).arg(b).arg(density);
		return str;
	} else if ( getIsocontourNodesHorizontal(x, y, &density) ) {
		str = QString("Isovalue = %1").arg(density);

		if (m_SpecArea && m_SpecMinVol && m_SpecMaxVol) {
			float area = 0;
			float minvolume = 0;
			float maxvolume = 0;
			area = getSpecFuncValue( 2, density );
			minvolume = getSpecFuncValue( 3, density );
			maxvolume = getSpecFuncValue( 4, density );

			str.append(QString("Isovalue = %1,\nArea = %2,\nMin volume = %3,\nMax volume = %4").arg(density).arg(area).arg(minvolume).arg(maxvolume) );
		}

		return str;
	}

	return str;
}


QRect XoomedIn::getMyRect() const
{
	QRect rect = contentsRect();
	//return rect;
	return QRect(rect.x()+5, rect.y()+5, rect.width()-10, rect.height()-10);

}

QPoint XoomedIn::transformToScreen(const QRect& rect, double density, double alpha) const
{
	return transformToScreen(rect,
							 getIntDensity(rect, density),
							 getIntAlpha(rect, alpha)
							);
}

QPoint XoomedIn::transformToScreen(const QRect& rect, int density, int alpha) const
{
	if (HorizontalMap) {
		if (InvertDensity) density = possibleFlip(density, rect.left(), rect.right());
		if (InvertAlpha) alpha = possibleFlip(alpha, rect.top(), rect.bottom());
		return QPoint(
					 density,
					 alpha
					 );
	} else {
		if (InvertDensity) density = possibleFlip(density, rect.top(), rect.bottom());
		if (InvertAlpha) alpha = possibleFlip(alpha, rect.left(), rect.right());
		return QPoint(
					 alpha,
					 density
					 );
	}
}

int XoomedIn::getIntDensity(const QRect& rect, double density) const
{
	if (HorizontalMap) {
		return mapToPixel(density, rect.left(), rect.right());
	} else {
		return mapToPixel(density, rect.top(), rect.bottom());
	}
}

int XoomedIn::getIntAlpha(const QRect& rect, double alpha) const
{
	if (HorizontalMap) {
		return mapToPixel(alpha, rect.top(), rect.bottom());
	} else {
		return mapToPixel(alpha, rect.left(), rect.right());
	}
}

double XoomedIn::getNormalizedDensity(const QRect& rect, const QPoint& source) const
{
	if (HorizontalMap) {
		return mapToDouble(getIntDensity(rect, source), rect.left(), rect.right(), m_RangeMin, m_RangeMax);
	} else {
		return mapToDouble(getIntDensity(rect, source), rect.top(), rect.bottom(), m_RangeMin, m_RangeMax);
	}
}

double XoomedIn::getNormalizedAlpha(const QRect& rect, const QPoint& source) const
{
	if (HorizontalMap) {
		return mapToDouble(getIntAlpha(rect, source), rect.top(), rect.bottom(), 0.0, 1.0);
	} else {
		return mapToDouble(getIntAlpha(rect, source), rect.left(), rect.right(), 0.0, 1.0);
	}
}

int XoomedIn::getIntDensity(const QRect& rect, const QPoint& source) const
{
	int density;
	int min, max;
	if (HorizontalMap) {
		density = source.x();
		min = rect.left(); max = rect.right();
	} else {
		density = source.y();
		min = rect.top(); max = rect.bottom();
	}
	// flip if necessary
	density = (InvertDensity?possibleFlip(density,min, max) :density);
	return density;
}

int XoomedIn::getIntAlpha(const QRect& rect, const QPoint& source) const
{
	int alpha;
	int min, max;
	if (HorizontalMap) {
		alpha = source.y();
		min = rect.top(); max = rect.bottom();
	} else {
		alpha = source.x();
		min = rect.left(); max = rect.right();
	}
	// flip if necessary
	alpha = (InvertAlpha?possibleFlip(alpha,min, max) :alpha);
	return alpha;
}

inline int XoomedIn::mapToPixel(double input, int start, int end) const
{
	if (input <= 1.0 && input >= 0.0) {
		int width = end-start;
		int offset = (int)(input * width);
		return start + offset;
	} else if (input > 1.0) {
		int width = end-start;
		int offset = (int)(1.0f * width);
		return start + offset;
	} else {
		int width = end-start;
		int offset = (int)(0.0f * width);
		return start + offset;
	}
}

inline double XoomedIn::mapToDouble(int input, int start, int end, double min, double max) const
{
	int width = end-start;
	int offset = input-start;

	return((double)offset/(double)width)*(max-min) + min;
}

inline int XoomedIn::possibleFlip(int input, int start, int end) const
{
	return end -  input + start;
}

inline void XoomedIn::normalizeFunc( float *func )
{
	float min=func[0], max=func[0];
	int i;

	// find the min and max
	for (i=0; i<256; i++) {
		if (func[i] < min) min = func[i];
		if (func[i] > max) max = func[i];
	}

	// normalize the values
	for (i=0; i<256; i++)
		func[i] = (func[i]-min) / (max-min);
}

void XoomedIn::paintEvent( QPaintEvent *event )
{
	QFrame::paintEvent(event);
	//drawContentsHorizontal( painter );

	QRect rect = contentsRect();
	QRect myRect = getMyRect();

	QPixmap  pix( rect.size() );	   // Pixmap for double-buffering

	QPalette palet = palette();
	QColor test = palet.color(QPalette::Normal, backgroundRole());

	pix.fill( test ); //this, rect.topLeft() );  // fill with widget background

	QPainter p( &pix );

	int i;
	drawColorMap(&p, myRect);
	if (m_DrawContourSpec)
		drawContourSpectrum(&p, myRect);
//	if (m_DrawContourTree)
//		drawContourTree(&p, myRect);

	for (i=0; i<m_TransferMap->getColorMap().size(); i++) {
		drawColorBar(&p, myRect, i);
	}

	for (i=0; i<m_TransferMap->getIsocontourMap().size(); i++) {
		drawIsocontourBar(&p, myRect, i);
	}

	if (m_DrawAlphaMap) {
		drawAlphaMap(&p, myRect);
		for (i=0; i<m_TransferMap->getAlphaMap().size(); i++) {
			drawAlphaNode(&p, myRect, i);
		}
	}

	QPainter painter(this); 
	painter.drawPixmap(rect.topLeft(), pix);
	//bitBlt( this, rect.topLeft(), &pix );
	p.end();

}

void XoomedIn::drawColorMap( QPainter* painter, QRect rect )
{
	double mag;
	int pos1, pos2;
	double doublePos1, doublePos2, clampedDoublePos1, clampedDoublePos2;
	double cr1, cr2;
	double cg1, cg2;
	double cb1, cb2;

	int minAlpha = getIntAlpha(rect, 0.0);
	int maxAlpha = getIntAlpha(rect, 1.0);

	int mapSize = m_TransferMap->getColorMap().size();
	for (int i=0; i<mapSize-1; i++) {
		doublePos1 = m_TransferMap->getColorMap().GetPosition(i+0);
		doublePos2 = m_TransferMap->getColorMap().GetPosition(i+1);

		if ( doublePos2 < m_RangeMin ) continue;
		if ( doublePos1 > m_RangeMax ) break;

		if ( doublePos1 < m_RangeMin ) {
			mag = (m_RangeMin-doublePos1)/(doublePos2-doublePos1);
			cr1 = m_TransferMap->getColorMap().GetRed(i) * (1.0-mag) + m_TransferMap->getColorMap().GetRed(i+1) * (mag);
			cg1 = m_TransferMap->getColorMap().GetGreen(i) * (1.0-mag) + m_TransferMap->getColorMap().GetGreen(i+1) * (mag);
			cb1 = m_TransferMap->getColorMap().GetBlue(i) * (1.0-mag) + m_TransferMap->getColorMap().GetBlue(i+1) * (mag);
			clampedDoublePos1 = m_RangeMin;
		} else {
			cr1 = m_TransferMap->getColorMap().GetRed(i);
			cg1 = m_TransferMap->getColorMap().GetGreen(i);
			cb1 = m_TransferMap->getColorMap().GetBlue(i);
			clampedDoublePos1 = doublePos1;
		}
		if ( doublePos2 > m_RangeMax ) {
			mag = (m_RangeMax-doublePos1)/(doublePos2-doublePos1);
			cr2 = m_TransferMap->getColorMap().GetRed(i) * (1.0-mag) + m_TransferMap->getColorMap().GetRed(i+1) * (mag);
			cg2 = m_TransferMap->getColorMap().GetGreen(i) * (1.0-mag) + m_TransferMap->getColorMap().GetGreen(i+1) * (mag);
			cb2 = m_TransferMap->getColorMap().GetBlue(i) * (1.0-mag) + m_TransferMap->getColorMap().GetBlue(i+1) * (mag);
			clampedDoublePos2 = m_RangeMax;
		} else {
			cr2 = m_TransferMap->getColorMap().GetRed(i+1);
			cg2 = m_TransferMap->getColorMap().GetGreen(i+1);
			cb2 = m_TransferMap->getColorMap().GetBlue(i+1);
			clampedDoublePos2 = doublePos2;
		}

		pos1 = getIntDensity(rect, (float)((clampedDoublePos1-m_RangeMin)/(m_RangeMax-m_RangeMin)));
		pos2 = getIntDensity(rect, (float)((clampedDoublePos2-m_RangeMin)/(m_RangeMax-m_RangeMin)));
		for (int y=pos1; y<=pos2; y++) {
			mag = mapToDouble(y, pos1, pos2);
			QColor color( mapToPixel(cr1, 0, 255)*(1.0-mag) + mapToPixel(cr2, 0, 255)*(mag),
						  mapToPixel(cg1, 0, 255)*(1.0-mag) + mapToPixel(cg2, 0, 255)*(mag),
						  mapToPixel(cb1, 0, 255)*(1.0-mag) + mapToPixel(cb2, 0, 255)*(mag));

			painter->setPen(color);
			QPoint p1 = transformToScreen(rect,y,minAlpha);
			QPoint p2 = transformToScreen(rect,y,maxAlpha);
			painter->drawLine(p1, p2);
		}
	}
}

void XoomedIn::drawColorBar( QPainter* painter, QRect rect, int index )
{
	double doublePos = m_TransferMap->getColorMap().GetPosition(index);
	QColor color(mapToPixel(1.0-m_TransferMap->getColorMap().GetRed(index), 0, 255),
				 mapToPixel(1.0-m_TransferMap->getColorMap().GetGreen(index), 0, 255),
				 mapToPixel(1.0-m_TransferMap->getColorMap().GetBlue(index), 0, 255));       

	int pos;
	int minAlpha = getIntAlpha(rect, 0.0);
	int maxAlpha = getIntAlpha(rect, 1.0);
	if (doublePos<m_RangeMin || doublePos>m_RangeMax) return;

	pos = getIntDensity(rect, (doublePos-m_RangeMin)/(m_RangeMax-m_RangeMin));

	painter->setPen(color);

	QPoint p1 = transformToScreen(rect,pos-1,minAlpha);
	QPoint p2 = transformToScreen(rect,pos-1,maxAlpha);
	painter->drawLine(p1.x(), p1.y(), p2.x(), p2.y());
	p1 = transformToScreen(rect,pos+1,minAlpha);
	p2 = transformToScreen(rect,pos+1,maxAlpha);
	painter->drawLine(p1.x(), p1.y(), p2.x(), p2.y());

	// get the screen point
	QPoint screenPoint = transformToScreen(rect, (doublePos-m_RangeMin)/(m_RangeMax-m_RangeMin), 0.0);

	// draw the rectangle
	QPoint topLeft(screenPoint); topLeft+=QPoint(-ALPHA_NODE_SIZE, -ALPHA_NODE_SIZE);
	painter->fillRect( topLeft.x(), topLeft.y(), ALPHA_NODE_SIZE*2+1, ALPHA_NODE_SIZE*2+1, Qt::red );
	painter->drawRect( topLeft.x(), topLeft.y(), ALPHA_NODE_SIZE*2+1, ALPHA_NODE_SIZE*2+1);

}

void XoomedIn::drawIsocontourBar( QPainter* painter, QRect rect, int index )
{
	double doublePos = m_TransferMap->getIsocontourMap().GetPositionOfIthNode(index);
	QColor color(mapToPixel(1.0-m_TransferMap->getColorMap().GetRed(doublePos), 0, 255),
				 mapToPixel(1.0-m_TransferMap->getColorMap().GetGreen(doublePos), 0, 255),
				 mapToPixel(1.0-m_TransferMap->getColorMap().GetBlue(doublePos), 0, 255));       

	int pos;
	int minAlpha = getIntAlpha(rect, 0.0);
	int maxAlpha = getIntAlpha(rect, 1.0);
	if (doublePos<m_RangeMin || doublePos>m_RangeMax) return;

	pos = getIntDensity(rect, (doublePos-m_RangeMin)/(m_RangeMax-m_RangeMin));

	painter->setPen(color);

	QPoint p1 = transformToScreen(rect,pos-1,minAlpha);
	QPoint p2 = transformToScreen(rect,pos-1,maxAlpha);
	painter->drawLine(p1.x(), p1.y(), p2.x(), p2.y());
	p1 = transformToScreen(rect,pos+1,minAlpha);
	p2 = transformToScreen(rect,pos+1,maxAlpha);
	painter->drawLine(p1.x(), p1.y(), p2.x(), p2.y());

	// get the screen point
	QPoint screenPoint = transformToScreen(rect, (doublePos-m_RangeMin)/(m_RangeMax-m_RangeMin), 1.0);

	// draw the rectangle
	QPoint topLeft(screenPoint); topLeft+=QPoint(-ALPHA_NODE_SIZE, -ALPHA_NODE_SIZE);
	painter->fillRect( topLeft.x(), topLeft.y(), ALPHA_NODE_SIZE*2+1, ALPHA_NODE_SIZE*2+1, Qt::green );
	painter->drawRect( topLeft.x(), topLeft.y(), ALPHA_NODE_SIZE*2+1, ALPHA_NODE_SIZE*2+1);
}

void XoomedIn::drawAlphaNode( QPainter* painter, QRect rect, int index )
{
	double doubleDensity = m_TransferMap->getAlphaMap().GetPosition(index);
	double doubleAlpha = m_TransferMap->getAlphaMap().GetAlpha(index);

	// throw away if off the visible range
	if (doubleDensity<m_RangeMin || doubleDensity>m_RangeMax) return;

	// get the screen point
	QPoint screenPoint = transformToScreen(rect, (doubleDensity-m_RangeMin)/(m_RangeMax-m_RangeMin), doubleAlpha);

	// draw the rectangle
	QPoint topLeft(screenPoint); topLeft+=QPoint(-ALPHA_NODE_SIZE, -ALPHA_NODE_SIZE);
	painter->fillRect( topLeft.x(), topLeft.y(), ALPHA_NODE_SIZE*2+1, ALPHA_NODE_SIZE*2+1, Qt::blue );
}

void XoomedIn::drawAlphaMap( QPainter* painter, QRect rect )
{
	int minAlpha = getIntAlpha(rect, 0.0);
	int maxAlpha = getIntAlpha(rect, 1.0);
	int minDensity = getIntDensity(rect, 0.0);
	int maxDensity = getIntDensity(rect, 1.0);

	double mag;
	double doublePos1, doublePos2, clampedDoublePos1, clampedDoublePos2;
	double alpha1, alpha2;

	int mapSize = m_TransferMap->getAlphaMap().size();
	for (int i=0; i<mapSize-1; i++) {
		doublePos1 = m_TransferMap->getAlphaMap().GetPosition(i+0);
		doublePos2 = m_TransferMap->getAlphaMap().GetPosition(i+1);

		// totally off screen, get next node
		if ( doublePos2 < m_RangeMin ) continue;
		// we went past the end of the range, break
		if ( doublePos1 > m_RangeMax ) break;

		// if needed find the intersection of the line with the min and max
		if ( doublePos1 < m_RangeMin ) {
			mag = (m_RangeMin-doublePos1)/(doublePos2-doublePos1);
			alpha1 = m_TransferMap->getAlphaMap().GetAlpha(i) * (1.0-mag) + m_TransferMap->getAlphaMap().GetAlpha(i+1) * (mag);
			clampedDoublePos1 = m_RangeMin;
		} else {
			alpha1 = m_TransferMap->getAlphaMap().GetAlpha(i);
			clampedDoublePos1 = doublePos1;
		}
		if ( doublePos2 > m_RangeMax ) {
			mag = (m_RangeMax-doublePos1)/(doublePos2-doublePos1);
			alpha2 = m_TransferMap->getAlphaMap().GetAlpha(i) * (1.0-mag) + m_TransferMap->getAlphaMap().GetAlpha(i+1) * (mag);
			clampedDoublePos2 = m_RangeMax;
		} else {
			alpha2 = m_TransferMap->getAlphaMap().GetAlpha(i+1);
			clampedDoublePos2 = doublePos2;
		}

		if ( alpha1 > alpha2 ) {
			double a1 = m_TransferMap->getAlphaMap().GetAlpha(i);
			double a2 = m_TransferMap->getAlphaMap().GetAlpha(i+1);         
		}
		QPoint screenPoint1 = transformToScreen(rect,(clampedDoublePos1-m_RangeMin)/(m_RangeMax-m_RangeMin), alpha1);
		QPoint screenPoint2 = transformToScreen(rect,(clampedDoublePos2-m_RangeMin)/(m_RangeMax-m_RangeMin), alpha2);

		// set the color to black
		painter->setPen(QColor(0,0,0));

		// draw the line
		painter->drawLine(screenPoint1.x(), screenPoint1.y(), screenPoint2.x(), screenPoint2.y());
	}
}

void XoomedIn::drawContourSpectrum( QPainter* painter, QRect rect )
{
	// m_RangeMin, m_RangeMax are in [0,1] (normalized X coords)
	// only render if the contour spectrum functions have been initialized
	if (m_SpecIsoval && m_SpecArea &&   m_SpecMinVol 
		&& m_SpecMaxVol && m_SpecGradient) {
		double curPos,nextPos, clampedCurPos,clampedNextPos,mag;
		double func1, func2;
		float *specFunc;
		int i,j;
		QColor funcColors[4];

		funcColors[0].setRgb(255,0,0); // area = dark red
		funcColors[1].setRgb(0,255,0); // min volume = dark green
		funcColors[2].setRgb(0,0,255); // max volume = dark blue
		funcColors[3].setRgb(255,255,0); // gradient = dark yellow


		for (j=0; j < 4; j++) {
			// pick a function to plot
			switch (j) {
			case 0:
				specFunc=m_SpecArea;
				break;
			case 1:
				specFunc=m_SpecMinVol;
				break;
			case 2:
				specFunc=m_SpecMaxVol;
				break;
			case 3:
				specFunc=m_SpecGradient;
				break;
			default:
				break;
			}

			// set the pen color to the current function's color
			painter->setPen(funcColors[j]);

			// loop through the function table
			for (i=0; i < 255; i++) {
				// current and next index normalized X coords
				curPos = i / 255.0;
				nextPos = (i+1) / 255.0;

				// make sure the points are within range
				if (nextPos < m_RangeMin) continue;
				if (curPos > m_RangeMax) break;

				// if needed find the intersection of the line with the min and max
				if ( curPos < m_RangeMin ) {
					mag = (m_RangeMin-curPos)/(nextPos-curPos);
					func1 = specFunc[i] * (1.0-mag) + specFunc[i+1] * (mag);
					clampedCurPos = m_RangeMin;
				} else {
					func1 = specFunc[i];
					clampedCurPos = curPos;
				}
				if ( nextPos > m_RangeMax ) {
					mag = (m_RangeMax-curPos)/(nextPos-curPos);
					func2 = specFunc[i] * (1.0-mag) + specFunc[i+1] * (mag);
					clampedNextPos = m_RangeMax;
				} else {
					func2 = specFunc[i+1];
					clampedNextPos = nextPos;
				}

				// create some endpoints
				QPoint screenPoint1 = transformToScreen(rect,(clampedCurPos-m_RangeMin)/(m_RangeMax-m_RangeMin), func1);
				QPoint screenPoint2 = transformToScreen(rect,(clampedNextPos-m_RangeMin)/(m_RangeMax-m_RangeMin), func2);


				// draw the line
				painter->drawLine(screenPoint1.x(), screenPoint1.y(), screenPoint2.x(), screenPoint2.y());

			}
		}
	}
}

/*void XoomedIn::drawContourTree( QPainter* painter, QRect rect )
{
	// m_RangeMin, m_RangeMax are in [0,1] (normalized X coords)
	// don't draw unless there is data to be drawn
	if (m_CTVerts && m_CTEdges) {
		double p1x,p1y, p2x,p2y;
		double clampedP1x,clampedP1y,clampedP2x,clampedP2y,mag;
		int i,numPoints=0;
		QPointArray points;

		// build up an array of line segments
		// float m_CTVerts.norm_x , m_CTVerts.func_val (isoval)
		// int m_CTEdges.v1 , m_CTEdges.v2
		for (i=0; i < m_NumCTEdges; i++) {

			// get the vertex coords
			p1x = m_CTVerts[m_CTEdges[i].v1].func_val;
			p1y = m_CTVerts[m_CTEdges[i].v1].norm_x;
			p2x = m_CTVerts[m_CTEdges[i].v2].func_val;
			p2y = m_CTVerts[m_CTEdges[i].v2].norm_x;

			//qDebug("(%f,%f) (%f,%f)", p1x,p1y,p2x,p2y);

			// don't add edges that are outside of the zoomed in region
			if (p1x < m_RangeMin && p2x < m_RangeMin) continue;
			if (p1x > m_RangeMax && p2x > m_RangeMax) continue;

			// find the cases where edges span the edge of our bounding box
			if (p1x < p2x) {
				if (p1x < m_RangeMin) {
					mag = (m_RangeMin-p1x)/(p2x-p1x);
					clampedP1y = p1y * (1.0-mag) + p2y * mag;
					clampedP1x = m_RangeMin;
				}
				else {
					clampedP1x = p1x;
					clampedP1y = p1y;
				}
				if (p2x > m_RangeMax) {
					mag = (m_RangeMax-p1x)/(p2x-p1x);
					clampedP2y = p1y * (1.0-mag) + p2y * mag;
					clampedP2x = m_RangeMax;
				}
				else {
					clampedP2x = p2x;
					clampedP2y = p2y;
				}
			}
			else { // p2x <= p1x (but probably p2x < p1x)
				if (p2x < m_RangeMin) {
					mag = (m_RangeMin-p2x)/(p1x-p2x);
					clampedP1y = p2y * (1.0-mag) + p1y * mag;
					clampedP1x = m_RangeMin;
				}
				else {
					clampedP1x = p2x;
					clampedP1y = p2y;
				}
				if (p1x > m_RangeMax) {
					mag = (m_RangeMax-p2x)/(p1x-p2x);
					clampedP2y = p2y * (1.0-mag) + p1y * mag;
					clampedP2x = m_RangeMax;
				}
				else {
					clampedP2x = p1x;
					clampedP2y = p1y;
				}
			}

			//qDebug("-> (%f,%f) (%f,%f)",clampedP1x,clampedP1y,clampedP2x,clampedP2y);
			
			QPoint P1 = transformToScreen(rect, (clampedP1x-m_RangeMin)/(m_RangeMax-m_RangeMin), clampedP1y),
						 P2 = transformToScreen(rect, (clampedP2x-m_RangeMin)/(m_RangeMax-m_RangeMin), clampedP2y);
			points.putPoints(numPoints, 2, P1.x(),P1.y(), P2.x(),P2.y());
			//qDebug("putPoints: (%f,%f), (%f,%f)", P1.x(),P1.y(), P2.x(),P2.y());
			numPoints += 2;
		}
		
		// set the pen color to black
		painter->setPen(QColor(0,0,0));
	
		// draw the Contour Tree
		painter->drawLineSegments(points);
	}
}
*/
void XoomedIn::showPopup(const QPoint& pos)
{
	popupMenu->popup(pos);
}

void XoomedIn::setMin( double min )
{
	m_RangeMin = min;
	update();
}

void XoomedIn::setMax( double max )
{
	m_RangeMax = max;
	update();
}

void XoomedIn::setSpectrumFunctions( float *isoval, float *area, float *min_vol, float *max_vol, float *gradient )
{
	// clean up previous functions
	if ( m_SpecIsoval) {
		free(m_SpecIsoval);
		m_SpecIsoval = 0;
	}
	if ( m_SpecArea ) {
		free(m_SpecArea);
		m_SpecArea = 0;
	}
	if ( m_SpecMinVol ) {
		free(m_SpecMinVol);
		m_SpecMinVol = 0;
	}
	if ( m_SpecMaxVol ) {
		free(m_SpecMaxVol);
		m_SpecMaxVol = 0;
	}
	if ( m_SpecGradient ) {
		free(m_SpecGradient);
		m_SpecGradient = 0;
	}

	// assign the new functions
	m_SpecIsoval = isoval;
	m_SpecArea = area;
	m_SpecMinVol = min_vol;
	m_SpecMaxVol = max_vol;
	m_SpecGradient = gradient;

	// those pointers might be NULL
	if (isoval && area && min_vol && max_vol && area) {
		//normalizeFunc( m_SpecGradient );
		//normalizeFunc( m_SpecIsoval );
		//normalizeFunc( m_SpecArea );
		//normalizeFunc( m_SpecMinVol );
		//normalizeFunc( m_SpecMaxVol );

		normalizeFunc( m_SpecGradient, 256, m_SpecFuncsMin[0], m_SpecFuncsMax[0] );
		normalizeFunc( m_SpecIsoval, 256, m_SpecFuncsMin[1], m_SpecFuncsMax[1] );
		normalizeFunc( m_SpecArea, 256, m_SpecFuncsMin[2], m_SpecFuncsMax[2] );
		normalizeFunc( m_SpecMinVol, 256, m_SpecFuncsMin[3], m_SpecFuncsMax[3] );
		normalizeFunc( m_SpecMaxVol, 256, m_SpecFuncsMin[4], m_SpecFuncsMax[4] );
	}

	// repaint if needed
	if (m_DrawContourSpec)
		update();
}

/*void XoomedIn::setCTGraph(int num_vtx, int num_edge, CTVTX* vtx_list, CTEDGE* edge_list)
{
	// free any old data
	if (m_CTVerts) {
		free(m_CTVerts);
		m_CTVerts = 0;
	}
	if (m_CTEdges) {
		free(m_CTEdges);
		m_CTEdges = 0;
	}

	// assign new data
	m_CTVerts = vtx_list;
	m_CTEdges = edge_list;
	m_NumCTVerts = num_vtx;
	m_NumCTEdges = num_edge;

	// normalize the vertices
	if (m_CTVerts) {			
		float xmin = m_CTVerts[0].norm_x, xmax=xmin,
					ymin = m_CTVerts[0].func_val, ymax=ymin;
		int i;
		
		for (i=0; i<m_NumCTVerts; i++) {
			if (m_CTVerts[i].func_val < ymin) ymin = m_CTVerts[i].func_val;
			else if (m_CTVerts[i].func_val > ymax) ymax = m_CTVerts[i].func_val;
			
			if (m_CTVerts[i].norm_x < xmin) xmin = m_CTVerts[i].norm_x;
			else if (m_CTVerts[i].norm_x > xmax) xmax = m_CTVerts[i].norm_x;
		}
		for (i=0; i<m_NumCTVerts; i++) {
			m_CTVerts[i].func_val = (m_CTVerts[i].func_val-ymin) / (ymax-ymin);
			m_CTVerts[i].norm_x = (m_CTVerts[i].norm_x-xmin) / (xmax-xmin);
		}
	}
	
	// repaint if needed
	if (m_DrawContourTree)
		update();
}
*/

bool XoomedIn::event(QEvent *event)
{
	if (event->type() == QEvent::ToolTip) {
		QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
		QString str = tip(helpEvent->pos());
		if (str.length() > 0) {
			QToolTip::showText(helpEvent->globalPos(), str);
		} else {
			QToolTip::hideText();
		}
	}
	return QWidget::event(event);
}

void XoomedIn::mouseMoveEvent( QMouseEvent* q )
{
	//mouseMoveEventHorizontal(q);
	double doubleAlpha, doubleDensity;

	QRect myRect = getMyRect();

	QPoint point = q->pos();

	if (m_MoveMode == DRAG_MOVE) {
		switch (m_SelectedType) {
		case COLOR_NODE:
			doubleDensity = getNormalizedDensity(myRect,point);
			if (doubleDensity<m_RangeMin) {
				doubleDensity=m_RangeMin;
			} else if (doubleDensity>m_RangeMax) {
				doubleDensity=m_RangeMax;
			}
			m_TransferMap->getColorMap().MoveNode(m_SelectedNode, doubleDensity);
			emit functionExploring();
			break;
		case ISOCONTOUR_NODE:
			doubleDensity = getNormalizedDensity(myRect,point);
			if (doubleDensity<m_RangeMin) {
				doubleDensity=m_RangeMin;
			} else if (doubleDensity>m_RangeMax) {
				doubleDensity=m_RangeMax;
			}
			m_TransferMap->getIsocontourMap().MoveIthNode(m_SelectedNode, doubleDensity);
			emit isocontourNodeExploring(m_TransferMap->getIsocontourMap().GetIDofIthNode(m_SelectedNode), doubleDensity);
			break;
		case ALPHA_NODE:
			doubleDensity = getNormalizedDensity(myRect,point);
			doubleAlpha = getNormalizedAlpha(myRect,point);

			if (doubleDensity<m_RangeMin) {
				doubleDensity=m_RangeMin;
			} else if (doubleDensity>m_RangeMax) {
				doubleDensity=m_RangeMax;
			}
			m_TransferMap->getAlphaMap().MoveNode(m_SelectedNode, doubleDensity);
			m_TransferMap->getAlphaMap().ChangeAlpha( m_SelectedNode, doubleAlpha);
			emit functionExploring();
			break;
		default:
			break;
		};
	}

	repaint();
	q->accept();
}

void XoomedIn::mousePressEvent( QMouseEvent* q )
{
	int x = q->x();
	int y = q->y();
	m_Positon = q->pos();

	if (q->button() == Qt::LeftButton ) {
		m_SelectedType = NO_NODE;
		m_SelectedNode = -1;
		if (selectNodes(x,y)) {
			m_MoveMode = DRAG_MOVE;
		}
	} else if (q->button() == Qt::MidButton && m_DrawContourTree) {
		// implement contour tree selection here
	} else if (q->button() == Qt::RightButton) {
		QPoint pos = mapToGlobal(q->pos());
		showPopup(pos);
	}

	update();
	q->accept();
}

void XoomedIn::mouseReleaseEvent( QMouseEvent* q )
{
	//mouseReleaseEventHorizontal(q);

	QPoint point = q->pos();
	m_Positon = point;

	QString verificationString;

	QRect myRect = getMyRect();

	double doubleDensity;

	if ( q->button() == Qt::LeftButton ) {
		if (m_MoveMode == DRAG_MOVE) {
			switch (m_SelectedType) {
			case COLOR_NODE:
				emit functionChanged();
				break;
			case ISOCONTOUR_NODE:
				{
					doubleDensity = getNormalizedDensity(myRect, point);
					double R = m_TransferMap->getColorMap().GetRed(doubleDensity);
					double G = m_TransferMap->getColorMap().GetGreen(doubleDensity);
					double B = m_TransferMap->getColorMap().GetBlue(doubleDensity);

					emit isocontourNodeChanged(m_TransferMap->getIsocontourMap().GetIDofIthNode(m_SelectedNode), m_TransferMap->getIsocontourMap().GetPositionOfIthNode(m_SelectedNode), R, G, B);
				}
				break;
			case ALPHA_NODE:
				emit functionChanged();
				break;
			default:
				break;
			};

			switch (m_SelectedType) {
			case COLOR_NODE:
				emit selectedColorNode();
				break;
			case ISOCONTOUR_NODE:
				{
					double doubleDensity = getNormalizedDensity(myRect,point);
					if (doubleDensity<m_RangeMin) {
						doubleDensity=m_RangeMin;
					} else if (doubleDensity>m_RangeMax) {
						doubleDensity=m_RangeMax;
					}
					emit selectedIsocontourNode(m_TransferMap->getIsocontourMap().GetIDofIthNode(m_SelectedNode), doubleDensity);
				}
				break;
			case ALPHA_NODE:
				emit selectedAlphaNode();
				break;
			default:
				break;
			};

		}

		m_MoveMode = NO_MOVE;
	}
	/*if ( q->button() == Qt::RightButton ) {
		POPUPSELECTION selection = LOAD_MAP; // = showPopup(q->globalPos());
		QString fileName;
		switch (selection) {
		case LOAD_MAP:
			loadMap();
			break;
		case SAVE_MAP:
			if (selectIsocontourNodes(q->x(), q->y())) {
				emit isocontourNodeSaved(m_TransferMap->getIsocontourMap().GetIDofIthNode(m_SelectedNode));
			} else {
				saveMap();
			}
			break;
		case ADD_COLOR:
			addColorNode();
			break;
		case ADD_ISOCONTOUR:
			addContourNode();
			break;
		case ADD_ALPHA:
			addAlphaNode();
			break;
		case EDIT_SELECTION:
			editSelectedNode();
			break;
		case DISP_CONTOUR_SPECTRUM:
			{
				// toggle the flag
				m_DrawContourSpec = !m_DrawContourSpec;
				// change the display
				if (m_DrawContourSpec) {
					// we need to draw the contour spectrum, make sure we have the data
					if (!m_SpecArea || !m_SpecMinVol || !m_SpecMaxVol || !m_SpecGradient) {
						// request the spectrum data
						emit acquireContourSpectrum();
					}
				}
				break;
			}
		case DISP_CONTOUR_TREE:
			{
				// toggle the flag
				m_DrawContourTree = !m_DrawContourTree ;
				// change the display
				if (m_DrawContourTree ) {
					// we need to draw the contour tree, make sure we have the data
//				if (!m_CTVerts || m_CTEdges) {
//					// request the contour tree data
//					emit acquireContourTree();
//				}
//
				}
				break;
			}
		case DISP_ALPHA_MAP:
			{
				// toggle the flag
				m_DrawAlphaMap = !m_DrawAlphaMap;
				// update is called after the switch()
				break;
			}
		case DELETE_SELECTION:
			deleteSelectedNode();
			break;
		default:
			break;
		}
	}*/
	update();

	q->accept();
}

void XoomedIn::contextMenuEvent( QContextMenuEvent* e)
{
	// block these messages for now
	e->accept();
}

bool XoomedIn::selectNodes( int x, int y )
{
	return selectAlphaNodes(x, y) || selectIsocontourNodes(x, y) || selectColorNodes(x, y);
}

bool XoomedIn::selectColorNodes( int x, int y )
{
	int curDistance = 1000000;
	int intNodeDensity, intQueryDensity, intDistance;
	int i;

	QRect myRect = getMyRect();
	if (y<myRect.y()-2 || y>myRect.bottom()+2 || x<myRect.x() || x>myRect.right() ) {
		return false;
	}

	intQueryDensity = getIntDensity(myRect, QPoint(x,y));

	for (i=0; i<this->m_TransferMap->getColorMap().size(); i++) {
		double doubleNodeDensity = m_TransferMap->getColorMap().GetPosition(i);
		intNodeDensity = getIntDensity(myRect, (doubleNodeDensity-m_RangeMin)/(m_RangeMax-m_RangeMin));
		intDistance = abs(intNodeDensity-intQueryDensity);
		if ( intDistance <= CLICK_RANGE && intDistance <curDistance) {
			curDistance = intDistance;
			m_SelectedNode = i;
			m_SelectedType = COLOR_NODE;
		}
	}
	if (m_SelectedType == COLOR_NODE) {
		return true;
	} else {
		return false;
	}
}

bool XoomedIn::selectIsocontourNodes( int x, int y )
{
	int curDistance = 1000000;
	int intNodeDensity, intQueryDensity, intDistance;
	int i;

	QRect myRect = getMyRect();
	if (y<myRect.y()-2 || y>myRect.bottom()+2 || x<myRect.x() || x>myRect.right() ) {
		return false;
	}

	intQueryDensity = getIntDensity(myRect, QPoint(x,y));

	for (i=0;i<this->m_TransferMap->getIsocontourMap().size();i++) {
		double doubleNodeDensity = m_TransferMap->getIsocontourMap().GetPositionOfIthNode(i);
		intNodeDensity = getIntDensity(myRect, (doubleNodeDensity-m_RangeMin)/(m_RangeMax-m_RangeMin));
		intDistance = abs(intNodeDensity-intQueryDensity);
		if ( intDistance <= CLICK_RANGE && intDistance <curDistance) {
			curDistance = intDistance;
			m_SelectedNode = i;
			m_SelectedType = ISOCONTOUR_NODE;
		}
	}
	if (m_SelectedType == ISOCONTOUR_NODE) {
		return true;
	} else {
		return false;
	}
}

bool XoomedIn::selectAlphaNodes( int x, int y )
{
	int curDistance = 1000000;
	int intManhattanDistance, intDistance;
	int i;
	int intQueryAlpha, intQueryDensity;
	int intNodeAlpha, intNodeDensity;

	QRect myRect = getMyRect();
	if (y<myRect.y()-2 || y>myRect.bottom()+2 ) {
		return false;
	}

	intQueryDensity = getIntDensity(myRect, QPoint(x,y));
	intQueryAlpha = getIntAlpha(myRect, QPoint(x,y));

	for (i=0;i<this->m_TransferMap->getAlphaMap().size();i++) {
		double doubleNodeDensity = m_TransferMap->getAlphaMap().GetPosition(i);
		intNodeDensity = getIntDensity(myRect, (doubleNodeDensity-m_RangeMin)/(m_RangeMax-m_RangeMin));
		intNodeAlpha = getIntAlpha(myRect, m_TransferMap->getAlphaMap().GetAlpha(i));
		intManhattanDistance = abs(intNodeDensity-intQueryDensity) + abs(intNodeAlpha-intQueryAlpha);
		intDistance = (abs(intNodeDensity-intQueryDensity) > abs(intNodeAlpha-intQueryAlpha) ? 
					   abs(intNodeDensity-intQueryDensity) : abs(intNodeAlpha-intQueryAlpha));
		if ( intDistance <= CLICK_RANGE && intManhattanDistance <curDistance) {
			curDistance = intManhattanDistance;
			m_SelectedNode = i;
			m_SelectedType = ALPHA_NODE;
		}
	}
	if (m_SelectedType == ALPHA_NODE) {
		return true;
	} else {
		return false;
	}
}

bool XoomedIn::selectCTEdge( int x, int y )
{
	return false;
}

bool XoomedIn::getColorNodesHorizontal( int x, int y, double* r, double* g, double* b, double* density )
{
	int curDistance = 1000000;
	int intPos, intDistance;
	int i;

	QRect rect = contentsRect();
	QRect reducedRect(rect.x()+5, rect.y()+10, rect.width()-10, rect.height()-20);
	if (y<reducedRect.y() || y>reducedRect.bottom() || x<rect.x()-2 || x>reducedRect.right()+2 ) {
		return false;
	}

	int intQueryDensity = getIntDensity(reducedRect, QPoint(x,y));

	for (i=0;i<this->m_TransferMap->getColorMap().size();i++) {
		double doublePos = m_TransferMap->getColorMap().GetPosition(i);
		intPos = getIntDensity( reducedRect, (doublePos-m_RangeMin)/(m_RangeMax-m_RangeMin) );
		//intDistance = abs(x-intPos);
		intDistance = (abs(intPos-intQueryDensity));
		if ( intDistance <= CLICK_RANGE && intDistance <curDistance) {
			curDistance = intDistance;
			// assign colors, rect, density
			*r = m_TransferMap->getColorMap().GetRed( i );
			*g = m_TransferMap->getColorMap().GetGreen( i );
			*b = m_TransferMap->getColorMap().GetBlue( i );
			//*density = getIntDensity( reducedRect, doublePos );
			*density = getDensityHorizontal( doublePos );
			return true;
		}
	}
	return false;
}

bool XoomedIn::getIsocontourNodesHorizontal( int x, int y, double* density )
{
	int curDistance = 1000000;
	int intPos, intDistance;
	int i;

	QRect rect = contentsRect();
	QRect reducedRect(rect.x()+5, rect.y()+10, rect.width()-10, rect.height()-20);
	if (y<reducedRect.y() || y>reducedRect.bottom() || x<rect.x()-2 || x>reducedRect.right()+2 ) {
		return false;
	}

	for (i=0;i<this->m_TransferMap->getIsocontourMap().size();i++) {
		double doublePos = m_TransferMap->getIsocontourMap().GetPositionOfIthNode(i);
		intPos = getIntDensity( reducedRect, doublePos );
		intDistance = abs(x-intPos);
		if ( intDistance <= CLICK_RANGE && intDistance <curDistance) {
			curDistance = intDistance;
			// assign colors, rect, density
			//*density = getIntDensity( reducedRect, doublePos );
			*density = getDensityHorizontal( doublePos );
			return true;
		}
	}
	return false;
}

bool XoomedIn::getAlphaNodesHorizontal( int x, int y, double* alpha, double* density)
{
	int curDistance = 1000000;
	int intManhattanDistance, intDistance;
	int i;
	int intYPos, intXPos;

	QRect rect = contentsRect();
	QRect reducedRect(rect.x()+5, rect.y()+10, rect.width()-10, rect.height()-20);
	if (x<reducedRect.x()-2 || x>reducedRect.right()+2 ) {
		return false;
	}

	for (i=0;i<this->m_TransferMap->getAlphaMap().size();i++) {
		double doublePos = m_TransferMap->getAlphaMap().GetPosition(i);
		intXPos = getIntDensity( reducedRect, m_TransferMap->getAlphaMap().GetPosition(i) );
		intYPos = mapToPixel(1.0-m_TransferMap->getAlphaMap().GetAlpha(i), reducedRect.y(), reducedRect.bottom());
		intManhattanDistance = abs(y-intYPos) + abs(x-intXPos);
		intDistance = (abs(y-intYPos) > abs(x-intXPos) ? abs(y-intYPos) : abs(x-intXPos));
		if ( intDistance <= CLICK_RANGE && intManhattanDistance <curDistance) {
			curDistance = intManhattanDistance;
			// assign alpha, rect, density
			*alpha = m_TransferMap->getAlphaMap().GetAlpha( i );
			//*density = getIntDensity( reducedRect, doublePos );
			*density = getDensityHorizontal( doublePos );
			return true;
		}
	}
	return false;
}

double XoomedIn::getDensityHorizontal( double x )
{
	return x *(m_MaxVal-m_MinVal) + m_MinVal;
}

void XoomedIn::setValExtents( double minVal, double maxVal )
{
	m_MinVal = minVal;
	m_MaxVal = maxVal;

	// try to load an appropriate color map
	if ( (m_MinVal >= 0) &&  (m_MaxVal >= 0) ) return; // default is good enough
	if ( (m_MinVal <= 0) &&  (m_MaxVal <= 0) ) return; // default is good enough? not really, but its a rare case SKVINAY

	// now see if there is a significant positive and negative portion 
	double relevanceFactor = 0.2;
	if ( (fabs(m_MinVal) < relevanceFactor*fabs(m_MaxVal)) || (fabs(m_MaxVal) < relevanceFactor*fabs(m_MinVal)) )
		return;	// the zero presence is not significant

	// make negative red, positive green
	// how close to 0 ?
	double length = fabs(m_MinVal) + fabs(m_MaxVal);
	double center = fabs(m_MinVal) / length;
	double minLength = ((fabs(m_MinVal) < fabs(m_MaxVal))? fabs(m_MinVal):fabs(m_MaxVal)) / length;

	double delta1 = 0.01, delta2 = 0.5;
	delta1 = minLength*delta1;
	delta2 = minLength*delta2;

	QString string = 
	QString("Anthony and Vinay are Great.\n")+
	QString("Alphamap\n")+
	QString("Number of nodes\n")+
	QString("6\n")+
	QString("Position and opacity\n")+
	QString("0 1\n")+
	QString("1 1\n")+
	QString::number( center-delta2 ) + QString(" 0.5\n") + 
	QString::number( center-delta1 ) + QString(" 0.0\n") + 
	QString::number( center+delta1 ) + QString(" 0.0\n") +
	QString::number( center+delta2 ) + QString(" 0.5\n") +
	QString("ColorMap\n")+
	QString("Number of nodes\n")+
	QString("5\n")+
	QString("Position and RGB\n")+
	QString("0 1 0 0\n")+
	QString("1 0 1 0\n")+
	QString::number( center-delta1 ) + QString(" 1 0 0\n") + 
	QString::number( center ) + QString(" 1 1 1\n") + 
	QString::number( center+delta1 ) + QString(" 0 1 0\n") + 
	QString("IsocontourMap\n")+
	QString("Number of nodes\n")+
	QString("0\n")+
	QString("Position\n");

	QTextStream stream(&string, QIODevice::ReadOnly);
	if (!m_TransferMap->loadColorTable(stream))
		return;	// some odd error
	else
		emit everythingChanged();
}

bool XoomedIn::removeIsocontourBar(int index)
{
	int i;

	for (i=0;i<this->m_TransferMap->getIsocontourMap().size();i++) {
		if ( m_TransferMap->getIsocontourMap().GetIDofIthNode(i) == index ) {
			m_TransferMap->getIsocontourMap().DeleteIthNode(i);
			return true;
		}
	}

	return false;
}

void XoomedIn::setDrawAlphaMap( bool setAlphaMapVisible )
{
	m_DrawAlphaMap = setAlphaMapVisible;
}

inline void XoomedIn::normalizeFunc( float *func, int len, float & min, float & max)
{
	//float min=func[0], max=func[0];
	int i;

	min = max = func[0];

	// find the min and max
	for (i=0; i<len; i++) {
		if (func[i] < min) min = func[i];
		if (func[i] > max) max = func[i];
	}

	// normalize the values
	for (i=0; i<len; i++)
		func[i] = (func[i]-min) / (max-min);
}

inline float XoomedIn::getSpecFuncValue( int func, double density )
{
	density = (density - m_MinVal) / (m_MaxVal - m_MinVal);

	float val = 0.0;

	if (func == 0) // special case for isovalue
		val = density*(m_MaxVal-m_MinVal)+m_MinVal;
	else if (func > 0 && func < 5) {
		switch ( func ) {
		case 0:
			val = m_SpecGradient[(int)(255*density)]*(m_SpecFuncsMax[func]-m_SpecFuncsMin[func])+m_SpecFuncsMin[func];
			break;
		case 1:
			val = m_SpecIsoval[(int)(255*density)]*(m_SpecFuncsMax[func]-m_SpecFuncsMin[func])+m_SpecFuncsMin[func];
			break;
		case 2:
			val = m_SpecArea[(int)(255*density)]*(m_SpecFuncsMax[func]-m_SpecFuncsMin[func])+m_SpecFuncsMin[func];
			break;
		case 3:
			val = m_SpecMinVol[(int)(255*density)]*(m_SpecFuncsMax[func]-m_SpecFuncsMin[func])+m_SpecFuncsMin[func];
			break;
		case 4:
			val = m_SpecMaxVol[(int)(255*density)]*(m_SpecFuncsMax[func]-m_SpecFuncsMin[func])+m_SpecFuncsMin[func];
			break;
		default:
			break;
		}
	}
	return val;
}

///////////////////////////////////////////////////////////////////////////////////////
// Public slots
///////////////////////////////////////////////////////////////////////////////////////
void XoomedIn::loadMap()
{
	QString fileName = QFileDialog::getOpenFileName(this, "open file dialog", "colormaps",
													"Colormap files (*.cmap)");
	if ( !(fileName.isNull()) ) {
		if (!m_TransferMap->loadColorTable(fileName)) {
			QMessageBox::warning( this, "Color Table",
								  "Error loading file: incorrect file format",
								  "Ok");
		} else {
			emit everythingChanged();
		}
	}
}

void XoomedIn::loadMap(const QString& fname)
{
	if (!m_TransferMap->loadColorTable(fname)) {
		QMessageBox::warning( this, "Color Table",
							  "Error loading file: incorrect file format",
							  "Ok");
	} else {
		emit everythingChanged();
	}
}

void XoomedIn::saveMap()
{
	QString fileName = QFileDialog::getSaveFileName(this, "open file dialog", "colormaps", 
													"Colormap files (*.cmap)");
	if ( !(fileName.isNull()) ) {
		if (!m_TransferMap->saveColorTable(fileName)) {
			QMessageBox::warning( this, "Color Table",
								  "Error saving file",
								  "Ok");
		}
	}
}

void XoomedIn::addColorNode()
{
	QRect myRect = getMyRect();
	double doubleDensity = getNormalizedDensity(myRect, m_Positon);
	if (doubleDensity<m_RangeMin) {
		doubleDensity=m_RangeMin;
	} else if (doubleDensity>m_RangeMax) {
		doubleDensity=m_RangeMax;
	}
	m_TransferMap->getColorMap().AddNode(doubleDensity);
	emit functionChanged();
}

void XoomedIn::addAlphaNode()
{
	QRect myRect = getMyRect();
	double doubleDensity = getNormalizedDensity(myRect, m_Positon);

	if (doubleDensity<m_RangeMin) {
		doubleDensity=m_RangeMin;
	} else if (doubleDensity>m_RangeMax) {
		doubleDensity=m_RangeMax;
	}
	m_TransferMap->getAlphaMap().AddNode(doubleDensity);
	emit functionChanged();
}

void XoomedIn::addContourNode()
{
	QRect myRect = getMyRect();
	double doubleDensity = getNormalizedDensity(myRect, m_Positon);
	if (doubleDensity<m_RangeMin) {
		doubleDensity=m_RangeMin;
	} else if (doubleDensity>m_RangeMax) {
		doubleDensity=m_RangeMax;
	}

	double R = m_TransferMap->getColorMap().GetRed(doubleDensity),
			   G = m_TransferMap->getColorMap().GetGreen(doubleDensity),
				   B = m_TransferMap->getColorMap().GetBlue(doubleDensity);

	int index = m_TransferMap->getIsocontourMap().AddNode(doubleDensity);

	if ( index != -1 ) emit isocontourNodeAdded(index, doubleDensity, R,G,B);
}

void XoomedIn::editSelectedNode()
{
	m_SelectedType = NO_NODE;
	m_SelectedNode = -1;
	if (selectColorNodes(m_Positon.x(), m_Positon.y())) {
		// edit a color node
		m_MoveMode = NO_MOVE;
		QColor color(mapToPixel(m_TransferMap->getColorMap().GetRed(m_SelectedNode), 0, 255),
					 mapToPixel(m_TransferMap->getColorMap().GetGreen(m_SelectedNode), 0, 255),
					 mapToPixel(m_TransferMap->getColorMap().GetBlue(m_SelectedNode), 0, 255));      
		QColor c = QColorDialog::getColor( color );
		if ( c.isValid() ) {
			m_TransferMap->getColorMap().ChangeColor(m_SelectedNode,
													 mapToDouble(c.red(), 0, 255),
													 mapToDouble(c.green(), 0, 255),
													 mapToDouble(c.blue(), 0, 255));
			emit functionChanged();
		}
	} else if (selectIsocontourNodes(m_Positon.x(), m_Positon.y())) {
		// edit an isocontour node (set its color)
		m_MoveMode = NO_MOVE;

		QColor color(mapToPixel(m_TransferMap->getIsocontourMap().GetRed(m_SelectedNode), 0, 255),
					 mapToPixel(m_TransferMap->getIsocontourMap().GetGreen(m_SelectedNode), 0, 255),
					 mapToPixel(m_TransferMap->getIsocontourMap().GetBlue(m_SelectedNode), 0, 255));

		QColor c = QColorDialog::getColor( color );
		if ( c.isValid() ) {
			double R,G,B;
			R = mapToDouble(c.red(), 0, 255);
			G = mapToDouble(c.green(), 0, 255);
			B = mapToDouble(c.blue(), 0, 255);

			m_TransferMap->getIsocontourMap().ChangeColor(m_SelectedNode, R, G, B);

			emit isocontourNodeColorChanged(m_TransferMap->getIsocontourMap().GetIDofIthNode(m_SelectedNode), R,G,B);
		}
	}
}

void XoomedIn::deleteSelectedNode()
{
	m_SelectedType = NO_NODE;
	m_SelectedNode = -1;
	if (selectNodes(m_Positon.x(), m_Positon.y())) {
		m_MoveMode = NO_MOVE;
		int ID;  
		switch (m_SelectedType) {
		case COLOR_NODE:
			m_TransferMap->getColorMap().DeleteNode(m_SelectedNode);
			emit functionChanged();
			break;
		case ISOCONTOUR_NODE:
			ID = m_TransferMap->getIsocontourMap().GetIDofIthNode(m_SelectedNode);
			m_TransferMap->getIsocontourMap().DeleteIthNode(m_SelectedNode);
			emit isocontourNodeDeleted(ID);
			break;
		case ALPHA_NODE:
			m_TransferMap->getAlphaMap().DeleteNode(m_SelectedNode);
			emit functionChanged();
			break;
		default:
			break;
		};
	}
	m_SelectedType = NO_NODE;
	m_SelectedNode = -1;
}